package com.wngbms.fileio.file_transfer;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import lombok.extern.slf4j.Slf4j;

import java.io.*;
import java.net.SocketAddress;
import java.nio.channels.FileChannel;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@Slf4j
public class FileReceiveHandler extends ChannelInboundHandlerAdapter {

    static Map<SocketAddress, FileClient> clientMap = new ConcurrentHashMap<>();

    public FileReceiveHandler() {
        log.info("init");
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//        log.info("msg:{}", msg);
        SocketAddress remoteAddress = ctx.channel().remoteAddress();

        FileClient client = clientMap.computeIfAbsent(remoteAddress, socketAddress -> {
            FileClient fileClient = new FileClient();
            fileClient.setSocketAddress(socketAddress);
            return fileClient;
        });

        ByteBuf buf = (ByteBuf) msg;
        process(buf, client);

        /**
         * 不能注释掉super调用, 否则会报泄露异常
         * 2022-01-08 17:01:31.083 [nioEventLoopGroup-3-1] [ERROR] io.netty.util.ResourceLeakDetector.reportTracedLeak[319] - LEAK: ByteBuf.release() was not called before it's garbage-collected. See https://netty.io/wiki/reference-counted-objects.html for more information.
         * Recent access records:
         * #1:
         * 	io.netty.buffer.AdvancedLeakAwareByteBuf.readBytes(AdvancedLeakAwareByteBuf.java:910)
         * 	com.wngbms.fileio.file_transfer.FileReceiveHandler.process(FileReceiveHandler.java:109)
         * 	com.wngbms.fileio.file_transfer.FileReceiveHandler.channelRead(FileReceiveHandler.java:36)
         */
        super.channelRead(ctx, msg);
    }

    public static void process(ByteBuf buf, FileClient client) throws IOException {
        while (true) {
            int readableBytes = buf.readableBytes();
//            log.info("readableBytes:{}", readableBytes);
            if (readableBytes <= 0)
                break;

            switch (client.step) {
                case 1:
                    log.info("step 1");
                    if (readableBytes < 4) {
                        log.info("文件名长度小于4字节:readableBytes:{}", readableBytes);
                        return;
                    }
                    int fileNameLength = buf.readInt();
                    client.setFileNameLength(fileNameLength);
                    client.setStep(2);
                    break;

                case 2:
                    log.info("step 2");
                    fileNameLength = client.getFileNameLength();
                    if (readableBytes < fileNameLength) {
                        log.info("文件名字节数{}小于指定长度{}", readableBytes, fileNameLength);
                        return;
                    }

                    byte[] fileNameBytes = new byte[fileNameLength];
                    buf.readBytes(fileNameBytes);

                    String fileName = new String(fileNameBytes, StandardCharsets.UTF_8);
                    client.setFileName(fileName);
                    log.info("文件名为:{}", fileName);

                    File dic = new File("J:\\copy");
                    if (!dic.exists()) {
                        dic.mkdir();
                    }

                    String fullName = dic.getAbsolutePath() + "\\" + fileName;
                    log.info("fullName:{}", fullName);
                    File file = new File(fullName);
                    if (!file.exists()) {
                        file.createNewFile();
                    }
                    FileChannel fileChannel = new FileOutputStream(file).getChannel();
                    client.setFileChannel(fileChannel);
                    client.setStep(3);

                    break;
                case 3:
                    log.info("step 3");
                    if (readableBytes < 4) {
                        log.info("文件长度小于4字节 readableBytes:{}", readableBytes);
                        return;
                    }

                    int fileLength = buf.readInt();
                    log.info("文件大小为:{}", fileLength);
                    client.setFileLength(fileLength);
                    client.setStep(4);
                    client.setStartTime(System.currentTimeMillis());

                    break;
                case 4:
//                    log.info("step 4");

                    long receivedLength = client.getReceivedLength();
                    buf.readBytes(client.getFileChannel(), receivedLength, readableBytes);
                    client.setReceivedLength(receivedLength + readableBytes);

//                    log.info("已接收文件大小:{}, 文件总大小:{}", client.getReceivedLength(), client.fileLength);


                    if (client.isFinished()) {
                        log.info("文件:{},接收成功", client.fileName);
                        log.info("传输时间:{}", System.currentTimeMillis() - client.startTime);
                        client.fileChannel.close();
                        log.info("文件输出通道关闭");
                        return;
                    }

                    break;
                default:
                    log.info("default ==============");
            }
        }
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        super.channelReadComplete(ctx);
    }

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        super.userEventTriggered(ctx, evt);
    }

    @Override
    public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
        super.channelWritabilityChanged(ctx);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        super.exceptionCaught(ctx, cause);
    }

    // ==============================================================================

    @Override
    public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
        log.info("{}", ctx);
        super.channelRegistered(ctx);
    }

    @Override
    public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
        log.info("{}", ctx);
        super.channelUnregistered(ctx);
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        log.info("{}", ctx);
        super.channelActive(ctx);
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        log.info("{}", ctx);
        super.channelInactive(ctx);
    }

}
